package lol.oxguy3.android.mcmc;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLEncoder;
import java.security.PublicKey;
import java.security.cert.Certificate;

import javax.net.ssl.HttpsURLConnection;

import lol.oxguy3.android.mcmc.utils.Base64;

import org.apache.http.client.ClientProtocolException;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

/**
 * Activity that runs at app startup and handles login screen
 */
public class MCMCActivity extends Activity implements View.OnClickListener {
	
	/**
	 * Constants for interpreting the response of authenticateMinecraftAccount().
	 * Each int corresponds to that String's location in the String[] that that function returns.
	 */
	public static final int AUTHMCACCT_USERNAME = 0;
	public static final int AUTHMCACCT_SESSIONID = 1;
	public static final int AUTHMCACCT_MCVERSIONTIMESTAMP = 2;
	
	static Context cxt;
	TickServiceConnection tsconn;
	
	public static String[] USERNAMES = {"oxguy3", "NyanAttack", "Notch"}; //TODO assemble these dynamically from a prefs file or something
	AutoCompleteTextView fieldUsername;
	EditText fieldPassword;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        cxt = this;
        
        
        fieldUsername = (AutoCompleteTextView) findViewById(R.id.editTextUsername);
        ArrayAdapter<String> usernameAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, USERNAMES);
        fieldUsername.setAdapter(usernameAdapter);
        
        
        fieldPassword = (EditText) findViewById(R.id.editTextPassword);
        
        
        Button btnLogin = (Button) findViewById(R.id.buttonLogin);
        btnLogin.setOnClickListener(this);
    }
    
    @Override
    public void onDestroy(){
    	cxt.unbindService(tsconn);
    }

	public void onClick(View v) {
		if (v.getId() == R.id.buttonLogin) {
			
			/** validate username (length is 2-16 inclusively, only accepts alphanumeric characters and underscores) */
			String user = fieldUsername.getText().toString();
			user = user.trim();
			if (user.length() < 2 || user.length() > 16) {
				Toast.makeText(cxt, "Username can only be 2-16 characters long.", Toast.LENGTH_SHORT).show();
				return;
			}
			if (!user.matches("([A-Za-z0-9_]+)")) {
				Toast.makeText(cxt, "Username can only contain letters, numbers and underscores.", Toast.LENGTH_SHORT).show();
				return;
			}
			
			/** validate password (length is 6-64 inclusively, all characters) */
			String pass = fieldPassword.getText().toString();
			if (pass.length() < 6 || pass.length() > 64) {
				Toast.makeText(cxt, "Password can only be 6-64 characters long.", Toast.LENGTH_SHORT).show();
				return;
			}
			
			String[] loginParams = authenticateMinecraftAccount(user, pass);
			if (loginParams == null) {
				return; /**the authenticateMinecraftAccount method will take care of toasting the error message, so no need to do so here */
			}
			
			
			SharedPreferences sprefs = cxt.getSharedPreferences("login", MODE_PRIVATE);
			SharedPreferences.Editor spedit = sprefs.edit();
			long mcversiontimestamp = Long.parseLong(loginParams[AUTHMCACCT_MCVERSIONTIMESTAMP]);
			
			if (sprefs.contains("mcversiontimestamp") && sprefs.getLong("mcversiontimestamp", 0) != mcversiontimestamp) {
				Toast.makeText(cxt, "A new version of Minecraft has been released. This app may need to be updated to connect to servers.", Toast.LENGTH_SHORT);
			}
			
			spedit.putString("username", loginParams[AUTHMCACCT_USERNAME]);
			spedit.putString("password", Base64.encodeBytes(pass.getBytes())); /** encoded with Base64 to prevent casual snooping */
			spedit.putString("sessionid", loginParams[AUTHMCACCT_SESSIONID]);
			spedit.putLong("mcversiontimestamp", mcversiontimestamp);
			
			spedit.apply();
			
			
			/** binds TickService */
			tsconn = new TickServiceConnection();
			if (!this.bindService(new Intent(this, TickService.class), tsconn, Context.BIND_AUTO_CREATE)) {
				Toast.makeText(cxt, "Fatal error: couldn't bind service", Toast.LENGTH_SHORT);
			}
			
			
			
			Intent prefIntent = new Intent(this, ServerListActivity.class);
	        startActivity(prefIntent);
			
		} else {
			Toast.makeText(cxt, "Unknown click event received from " + v.toString(), Toast.LENGTH_SHORT);
		}
	}
	
	/**
	 * Authenticates given login details with the Minecraft.net authentication servers
	 * 
	 * A large part of this code comes from https://github.com/dennis6981/Minecraft-Multi-Launcher/tree/master/src/net/minecraft 
	 * although I'm pretty sure it originated in the official Minecraft launcher
	 * 
	 * @param username Minecraft.net username, not case-sensitive
	 * @param password Minecraft.net password, presumably case-sensitive
	 * @return a String array with a case-correct version of the username, the session ID, and the current Minecraft version timestamp
	 */
	public static String[] authenticateMinecraftAccount(String username, String password) {
		
		String[] returnArray = new String[3];
		HttpsURLConnection connection = null;
		
	    try {
	    	String urlParameters = "user=" + URLEncoder.encode(username, "UTF-8") + "&password=" + URLEncoder.encode(password, "UTF-8") + "&version=" + 13;
	    	
	    	URL url = new URL("https://login.minecraft.net/");
			connection = (HttpsURLConnection) url.openConnection();
			
			connection.setRequestMethod("POST");
			connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
			connection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length));
			connection.setRequestProperty("Content-Language", "en-US");

			connection.setUseCaches(false);
			connection.setDoInput(true);
			connection.setDoOutput(true);

			connection.connect();
			
			
			Certificate[] certs = connection.getServerCertificates();
			AssetManager am = cxt.getAssets();
			byte[] bytes = new byte[294];
			
			DataInputStream dis = new DataInputStream(am.open("minecraft.key")/*Util.class.getResourceAsStream("minecraft.key")*/);
			dis.readFully(bytes);
			dis.close();

			Certificate c = certs[0];
			PublicKey pk = c.getPublicKey();
			byte[] data = pk.getEncoded();

			for (int i = 0; i < data.length; i++) {
				if (data[i] == bytes[i])
					continue;
				throw new RuntimeException("Public key mismatch");
			}

			DataOutputStream wr = new DataOutputStream(connection.getOutputStream());
			wr.writeBytes(urlParameters);
			wr.flush();
			wr.close();

			InputStream is = connection.getInputStream();
			BufferedReader rd = new BufferedReader(new InputStreamReader(is));

			StringBuffer response = new StringBuffer();
			String line;
			while ((line = rd.readLine()) != null) {
				response.append(line);
				response.append('\r');
			}
			rd.close();
			
			
			
			String unparsedResponse = response.toString();
	    	
	        if (unparsedResponse.equalsIgnoreCase("Old Version")) {
	        	throw new Exception("Outdated client protocol; app needs to be updated.");
	        	//Toast.makeText(cxt, "Error: Outdated client protocol; app needs to be updated.", Toast.LENGTH_SHORT).show();
	        	//return null;
	        	
	        } else if (unparsedResponse.equalsIgnoreCase("Bad request")) {
	        	throw new Exception("Server replied '" + unparsedResponse + "'. (did you leave something blank?)");
	        	//Toast.makeText(cxt, "Error: Server replied '" + unparsedResponse + "'. (did you leave something blank?)", Toast.LENGTH_SHORT).show();
	        	//return null;
	        	
	        } else if (unparsedResponse.equalsIgnoreCase("Bad login")) {
	        	throw new Exception("Incorrect username or password.");
	        	//Toast.makeText(cxt, "Error: Incorrect username or password.", Toast.LENGTH_SHORT).show();
	        	//return null;
	        	
	        }
	        
	        String[] respParams = unparsedResponse.split(":");
	        
	        /** if there aren't 4 parameters returned, the response is bad and it should feel bad */
	        if (respParams.length != 4) {
		        throw new IOException("Wrong number of parameters in returned string");
	        }
	        /** verify that the Minecraft version timestamp is a valid long (it'll throw a NumberFormatException if not) */
	        Long.parseLong(respParams[0]); 
	        //Long.parseLong(respParams[3]);
	        
	        
	        returnArray[AUTHMCACCT_USERNAME] = respParams[2]; /** case-correct username */
	        returnArray[AUTHMCACCT_SESSIONID] = respParams[3]; /** Minecraft.net session ID */
	        returnArray[AUTHMCACCT_MCVERSIONTIMESTAMP] = respParams[0]; /** timestamp corresponding to the current version of Minecraft */
	        
			return returnArray;
	        
	        
	    } catch (Exception e) {
			e.printStackTrace();
			
			if (e instanceof ClientProtocolException) {
	    		Toast.makeText(cxt, "Error: Client protocol error", Toast.LENGTH_SHORT).show();
	    		
	    	} else if (e instanceof IOException || e instanceof NumberFormatException) {
	    		Toast.makeText(cxt, "Error: Bad response from minecraft.net.\nAuthentication server may be down; check mc.nexua.org.", Toast.LENGTH_SHORT).show();
	    		
	    	} else {
	    		if (e.getMessage()!=null) {
		    		Toast.makeText(cxt, "Error: " + e.getMessage(), Toast.LENGTH_SHORT).show();
	    		} else {
		    		Toast.makeText(cxt, "An unknown error occurred! Shutting down the app...", Toast.LENGTH_SHORT).show();
		    		System.exit(1);
	    		}
	    	}
			return null;
			
		} finally {
			if (connection != null) {
				connection.disconnect();
			}
		}
	}
}